/*
 * Copyright (c) 2024 Black Sesame Technologies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <iostream>
#include <dlfcn.h>
#include <gnu/lib-names.h>
#include "ndds/ndds_c.h"
#include "ndds/dds_c/dds_c_infrastructure_impl.h"
#include "dal/allocator.h"
#include "dal/config_loader.h"
#include "dal_rticonnextdds/converter/StatusConverter.hpp"
#include "dal_rticonnextdds/converter/StatusMaskConverter.hpp"
#include "dal_rticonnextdds/converter/QosConverter.hpp"
#include "dal_rticonnextdds/converter/RetTypeConverter.hpp"
#include "dal_rticonnextdds/converter/GuidConverter.hpp"
#include "dal_rticonnextdds/listener_impl/DomainParticipantListenerImpl.hpp"
#include "dal_rticonnextdds/utils/log.hpp"
#include "dal_rticonnextdds/utils/EntityGetter.hpp"
#include "dal_rticonnextdds/utils/ParticipantMonitor.hpp"
#include "dal_rticonnextdds/utils/EntityMappingManager.hpp"

static dal_ret_t delete_rtidds_participant(DDS_DomainParticipant* r_participant)
{
    DDS_DomainParticipantFactory* factory = DDS_DomainParticipantFactory_get_instance();
    DDS_ReturnCode_t retcode =
        DDS_DomainParticipantFactory_delete_participant(factory, r_participant);
    if (retcode != DDS_RETCODE_OK) {
        LOG_ERROR() << "rtidds delete_participant error " << retcode << LOG_ENDL;
        return DAL_RETCODE_ERROR;
    }
    return DAL_RETCODE_OK;
}

dal_domain_participant_listener_t*
dal_create_domain_participant_listener(dal_domain_participant_listener_callbacks_t callbacks)
{
    dal_domain_participant_listener_t* listener = dal_domain_participant_listener_allocate();
    if (listener == nullptr) {
        return nullptr;
    }
    listener->handle_ = (new DomainParticipantListenerImpl(callbacks));
    if (!listener->handle_) {
        LOG_ERROR() << "DomainParticipantListenerImpl memory allocation failed." << LOG_ENDL;
        dal_domain_participant_listener_free(listener);
        return nullptr;
    }

    return listener;
}

dal_ret_t dal_delete_domain_participant_listener(dal_domain_participant_listener_t* listener)
{
    if (!listener || !listener->handle_) {
        LOG_ERROR_WITH_LOCATION() << "listener is invalid." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    DomainParticipantListenerImpl* listener_impl =
        reinterpret_cast<DomainParticipantListenerImpl*>(listener->handle_);
    if (listener_impl->ref_count_.Get() > 0) {
        LOG_ERROR() << "can't delete domain participant listener because it's entity still exist."
                    << LOG_ENDL;
        return DAL_RETCODE_PRECONDITION_NOT_MET;
    }

    delete listener_impl;
    dal_domain_participant_listener_free(listener);
    reinterpret_cast<dal_domain_participant_listener_t*>(listener)->handle_ = nullptr;

    return DAL_RETCODE_OK;
}



dal_domain_participant_t* dal_create_domain_participant(dal_domain_id domain_id,
                                                        dal_domain_participant_qos_t* qos,
                                                        dal_domain_participant_listener_t* listener,
                                                        dal_status_mask_t mask)
{
    dal_ret_t d_ret = DAL_RETCODE_OK;

    if (domain_id < 0 || domain_id > DAL_DOMAIN_ID_MAX) {
        LOG_ERROR_WITH_LOCATION()
            << "domain_id is invalid, a number between 0 and 160 is recommended" << LOG_ENDL;
        return nullptr;
    }

    dal_domain_participant_t* participant = dal_domain_participant_allocate();
    if (!participant) {
        LOG_ERROR_WITH_LOCATION() << "dal domain_participant allocate failed." << LOG_ENDL;
        return nullptr;
    }

    DDS_DomainParticipantFactory* factory = DDS_DomainParticipantFactory_get_instance();
    DDS_DomainParticipantQos* r_qos = new DDS_DomainParticipantQos();
    DDS_DomainParticipantFactory_get_default_participant_qos(factory, r_qos);
    d_ret = QosConverter::GetInstance()->ConvertParticipantQosFromDalToRtidds(qos, r_qos);
    if (DAL_RETCODE_OK != d_ret) {
        LOG_ERROR_WITH_LOCATION() << "convert domain participant qos failed." << LOG_ENDL;
        dal_domain_participant_free(participant);
        return nullptr;
    }

    const char* env_enable = getenv("CYBER_DAL_ENABLE");
    if (env_enable && strcmp(env_enable, "true") == 0) {
        QosConverter::GetInstance()->OptimizeParticipantQosInCyberRT(r_qos);
    }

    DomainParticipantListenerImpl* r_listener_impl =
        listener ? reinterpret_cast<DomainParticipantListenerImpl*>(listener->handle_) : nullptr;

    DDS_StatusMask r_mask = DDS_STATUS_MASK_NONE;
    d_ret = StatusMaskConverter::GetInstance()->ConvertStatusMaskFromDalToRtidds(mask, &r_mask);
    if (DAL_RETCODE_OK != d_ret) {
        LOG_ERROR_WITH_LOCATION() << "convert status mask failed." << LOG_ENDL;
        dal_domain_participant_free(participant);
        return nullptr;
    }

    rti_participant_discovery_info_s participant_info;
    participant_info.born_time = rti_monitor_timestamp();
    participant_info.meta = 1;
    DDS_OctetSeq_from_array(&r_qos->user_data.value,
                            (DDS_Octet*)&participant_info,
                            sizeof(rti_participant_discovery_info_s));

    DDS_DomainParticipant* r_participant = DDS_DomainParticipantFactory_create_participant(
        factory, domain_id, r_qos, r_listener_impl ? r_listener_impl->getListener() : NULL, r_mask);

    delete r_qos;
    if (!r_participant) {
        LOG_ERROR_WITH_LOCATION() << "create domain_participant failed." << LOG_ENDL;
        dal_domain_participant_free(participant);
        return nullptr;
    }

    QosConverter::GetInstance()->CreateFlowController(r_participant);

    DalDomainParticipantImpl* participant_impl =
        new DalDomainParticipantImpl(r_participant, r_listener_impl);
    if (!participant_impl) {
        LOG_ERROR_WITH_LOCATION() << "create domain_participant_impl failed." << LOG_ENDL;
        dal_domain_participant_free(participant);
        delete_rtidds_participant(r_participant);
        return nullptr;
    }

    if (r_listener_impl != nullptr && r_listener_impl->is_participant_monitor_callback_set()) {
        participant_impl->enable_participant_monitor();
        std::thread th(rti_participant_monitor, participant_impl);
        participant_impl->set_participant_monitor(th);
    }

    EntityMappingAddDomainParticipant(r_participant, participant);
    participant->handle_ = participant_impl;
    return participant;
}

dal_domain_participant_t*
dal_create_domain_participant_with_profile(dal_domain_id domain_id,
                                           dal_domain_participant_qos_t* qos,
                                           const char* profile_name,
                                           dal_domain_participant_listener_t* listener,
                                           dal_status_mask_t mask)
{
    if (!profile_name || strlen(profile_name) == 0) {
        return dal_create_domain_participant(domain_id, qos, listener, mask);
    }

    dal_ret_t d_ret = DAL_RETCODE_OK;

    if (domain_id < 0 || domain_id > DAL_DOMAIN_ID_MAX) {
        LOG_ERROR_WITH_LOCATION()
            << "domain_id is invalid, a number between 0 and 160 is recommended" << LOG_ENDL;
        return nullptr;
    }

    dal_domain_participant_t* participant = dal_domain_participant_allocate();
    if (!participant) {
        LOG_ERROR_WITH_LOCATION() << "dal domain_participant allocate failed." << LOG_ENDL;
        return nullptr;
    }

    DDS_DomainParticipantFactory* factory = DDS_DomainParticipantFactory_get_instance();
    DDS_DomainParticipantQos* r_qos = new DDS_DomainParticipantQos();
    DDS_ReturnCode_t r_ret = DDS_DomainParticipantFactory_get_participant_qos_from_profile(
        factory, r_qos, "dal_rticonnext_qos_profile_lib", profile_name);
    if (r_ret != DDS_RETCODE_OK) {
        d_ret = QosConverter::GetInstance()->ConvertParticipantQosFromDalToRtidds(qos, r_qos);
        if (DAL_RETCODE_OK != d_ret) {
            LOG_ERROR_WITH_LOCATION() << "convert domain participant qos failed." << LOG_ENDL;
            dal_domain_participant_free(participant);
            return nullptr;
        }
    }

    // set participant name if exists in qos.
    if (!r_qos->participant_name.name || strlen(r_qos->participant_name.name) == 0) {
        if (qos->participant_name && strlen(qos->participant_name) > 0) {
            r_qos->participant_name.name = (char*)malloc(strlen(qos->participant_name) + 1);
            memcpy(
                r_qos->participant_name.name, qos->participant_name, strlen(qos->participant_name));
            (r_qos->participant_name.name)[strlen(qos->participant_name)] = '\0';
        }
    }

    // reduce shutdown_cleanup_period in DATABASE QosPolicy in ParticipantQos
    r_qos->database.shutdown_cleanup_period.sec = 0;
    r_qos->database.shutdown_cleanup_period.nanosec = 50000000;

    DomainParticipantListenerImpl* r_listener_impl =
        listener ? reinterpret_cast<DomainParticipantListenerImpl*>(listener->handle_) : nullptr;

    DDS_StatusMask r_mask = DDS_STATUS_MASK_NONE;
    d_ret = StatusMaskConverter::GetInstance()->ConvertStatusMaskFromDalToRtidds(mask, &r_mask);
    if (DAL_RETCODE_OK != d_ret) {
        LOG_ERROR_WITH_LOCATION() << "convert status mask failed." << LOG_ENDL;
        dal_domain_participant_free(participant);
        return nullptr;
    }

    rti_participant_discovery_info_s participant_info;
    participant_info.born_time = rti_monitor_timestamp();
    participant_info.meta = 1;
    DDS_OctetSeq_from_array(&r_qos->user_data.value,
                            (DDS_Octet*)&participant_info,
                            sizeof(rti_participant_discovery_info_s));

    DDS_DomainParticipant* r_participant = DDS_DomainParticipantFactory_create_participant(
        factory, domain_id, r_qos, r_listener_impl ? r_listener_impl->getListener() : NULL, r_mask);

    delete r_qos;
    if (!r_participant) {
        LOG_ERROR_WITH_LOCATION() << "create domain_participant failed." << LOG_ENDL;
        dal_domain_participant_free(participant);
        return nullptr;
    }

    QosConverter::GetInstance()->CreateFlowController(r_participant);

    DalDomainParticipantImpl* participant_impl =
        new DalDomainParticipantImpl(r_participant, r_listener_impl);
    if (!participant_impl) {
        LOG_ERROR_WITH_LOCATION() << "create domain_participant_impl failed." << LOG_ENDL;
        dal_domain_participant_free(participant);
        delete_rtidds_participant(r_participant);
        return nullptr;
    }

    if (r_listener_impl != nullptr && r_listener_impl->is_participant_monitor_callback_set()) {
        participant_impl->enable_participant_monitor();
        std::thread th(rti_participant_monitor, participant_impl);
        participant_impl->set_participant_monitor(th);
    }

    EntityMappingAddDomainParticipant(r_participant, participant);
    participant->handle_ = participant_impl;
    return participant;
}

dal_ret_t dal_delete_domain_participant(dal_domain_participant_t* participant)
{
    DDS_DomainParticipant* r_participant = get_rtidds_domain_participant(participant);
    if (!r_participant) {
        LOG_ERROR_WITH_LOCATION() << "get rtidds participant failed." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    DalDomainParticipantImpl* participant_impl =
        reinterpret_cast<DalDomainParticipantImpl*>(participant->handle_);
    if (participant_impl->get_participant_monitor_flag().load()) {
        participant_impl->disable_participant_monitor();
    }

    dal_ret_t d_ret = delete_rtidds_participant(r_participant);
    if (d_ret != DAL_RETCODE_OK) {
        LOG_ERROR_WITH_LOCATION() << "delete rtidds domain participant failed." << LOG_ENDL;
        return d_ret;
    }

    EntityMappingRemoveDomainParticipant(r_participant);

    delete participant_impl;
    dal_domain_participant_free(participant);
    reinterpret_cast<dal_domain_participant_t*>(participant)->handle_ = nullptr;
    return DAL_RETCODE_OK;
}

dal_guid_t dal_domain_participant_get_guid(dal_domain_participant_t* participant)
{
    dal_guid_t d_guid = { };
    DDS_InstanceHandle_t r_ins_handle;
    DDS_DomainParticipant* r_participant = get_rtidds_domain_participant(participant);
    if (!r_participant) {
        LOG_ERROR_WITH_LOCATION() << "get rtidds participant failed." << LOG_ENDL;
        return d_guid;
    }

    r_ins_handle = DDS_Entity_get_instance_handle(DDS_DomainParticipant_as_entity(r_participant));
    DDS_GUID_t r_guid;
    memcpy(r_guid.value, r_ins_handle.keyHash.value, DDS_GUID_LENGTH);
    GuidConverter::GetInstance()->ConvertGuidFromRtiddsToDal(&r_guid, &d_guid);
    return d_guid;
}

dal_ret_t dal_domain_participant_listener_set_participant_monitor_callback(
    dal_domain_participant_listener_t* listener,
    dal_participant_monitor_callback callback,
    void* callback_data)
{
    if (!listener || !listener->handle_) {
        LOG_ERROR_WITH_LOCATION() << "listener is invalid." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }
    auto listener_impl = reinterpret_cast<DomainParticipantListenerImpl*>(listener->handle_);
    listener_impl->set_participant_monitor_callback(callback);
    listener_impl->set_participant_monitor_callback_data(callback_data);
    return DAL_RETCODE_OK;
}

dal_ret_t dal_domain_participant_delete_contained_entities(dal_domain_participant_t* participant)
{
    DDS_DomainParticipant* r_participant = get_rtidds_domain_participant(participant);
    if (!r_participant) {
        LOG_ERROR_WITH_LOCATION() << "get rtidds participant failed." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    DDS_ReturnCode_t retcode = DDS_DomainParticipant_delete_contained_entities(r_participant);
    if (retcode == DDS_RETCODE_OK) {
        return DAL_RETCODE_OK;
    }
    return DAL_RETCODE_PRECONDITION_NOT_MET;
}
